#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author : hai.long
import asyncio
import datetime
import json
import os
import re
import time
import shutil
import requests
import traceback
from skimage.metrics import structural_similarity as ssim
from skimage import io
from skimage.transform import resize
import numpy as np
from uuid import uuid4
from utils import log_tool
from utils.random_tool import my_faker
from utils.base_request import BaseRequest
from playwright.async_api import Playwright, Page
from utils.common import now, compare, file_exists, upload_file_to_oss, upload_file_to_simple_drawing

# 获取目录
UTILS_DIR = os.path.dirname(__file__)
# 截取项目路径
ROOT_DIR = os.path.dirname(UTILS_DIR)
BASE_PW_DIR = './'


class SmartPlaywright(object):
    page: Page = None
    debug = False
    websocket = None
    log_text = ""
    image_urls = []
    global_var_pool = {}
    env_var_pool = {}
    case_var_pool = {}
    extract_state = False
    extract_path = ''

    chromium_user_data_dir = f"{ROOT_DIR}/ms-playwright/chromium-1091/user_data"  # 指定缓存目录，用相对路径
    chromium_executable_path = f"{ROOT_DIR}/ms-playwright/chromium-1091/chrome-win/chrome.exe"  # 指定可执行文件路径，用相对路径

    firefox_user_data_dir = f"{ROOT_DIR}/ms-playwright/firefox-1429/firefox/user_data"  # 指定缓存目录，用相对路径
    firefox_executable_path = f"{ROOT_DIR}/ms-playwright/firefox-1429/firefox/firefox.exe"  # 指定可执行文件路径，用相对路径

    wk_user_data_dir = f"{ROOT_DIR}/ms-playwright/webkit-1921/user_data"  # 指定缓存目录，用相对路径
    wk_executable_path = f"{ROOT_DIR}/ms-playwright/webkit-1944/Playwright.exe"  # 指定可执行文件路径，用相对路径

    def __init__(self, playwright: Playwright):
        self.playwright = playwright

    async def init(self, browser_name="debug", headless=False):
        if os.path.exists(self.chromium_user_data_dir):
            shutil.rmtree(self.chromium_user_data_dir)
        await self.log_info(f"操作: 正在启动浏览器。。。, 模式: {browser_name}")
        if browser_name == "chromium" or browser_name == "chromium-ss":
            self.browser = await self.playwright.chromium.launch_persistent_context(
                user_data_dir=self.chromium_user_data_dir,
                executable_path=self.chromium_executable_path,
                headless=headless,
                no_viewport=True,
                viewport={'width': 1920, 'height': 1080},
                args=["--start-maximized", ]
            )
            # os.environ.setdefault('PLAYWRIGHT_BROWSERS_PATH', f'{BASE_PW_DIR}/ms-playwright')
            # self.browser = await self.playwright.chromium.launch(headless=headless, args=["--start-maximized", ])
            # self.context = await self.browser.new_context(no_viewport=True)
            # self.context.set_default_timeout(10000)
            # self.page = await self.context.new_page()
        elif browser_name == "debug":
            self.browser = await self.playwright.chromium.connect_over_cdp("http://localhost:9222")
            # print("所有context：", self.browser.contexts[0].pages)
            self.context = self.browser.contexts[0]
            self.page = self.context.pages[0]
            self.context.set_default_timeout(60000)
            await self.log_info(f"操作: 启动浏览器成功, 获取会话: {str(self.context)}")
        elif browser_name == "firefox":
            self.browser = await self.playwright.firefox.launch_persistent_context(
                user_data_dir=self.firefox_user_data_dir,
                executable_path=self.firefox_executable_path,
                headless=headless,
                no_viewport=True,
                args=["--start-maximized", ]
            )
        else:
            self.browser = await self.playwright.webkit.launch(headless=headless, args=["--start-maximized", ])
        # 配置启动参数
        if browser_name != "debug":
            if browser_name == "chromium-ss":
                # 从文件中读取恢复存储状态，实现自动登录
                try:
                    storage_state_path = f"./apps/web/storage_state/snkd_test_storage_state.json"
                    self.context = await self.browser.new_context(storage_state=storage_state_path)
                    await self.log_info(f"操作: 从文件中读取恢复存储状态, storage_state: {storage_state_path}")
                    self.page = await self.context.new_page()
                except Exception as e:
                    traceback.print_exc()
                    await self.log_info(
                        f"操作: 从文件中读取恢复存储状态失败, 备注: 请检查storage_state文件, 异常信息: {str(e)}")
            else:
                print("操作: 设置浏览器超时时间, 超时时间: 60000ms")
                await self.log_info("操作: 设置浏览器超时时间, 超时时间: 60000ms")
                # self.context.tracing.start(screenshots=True, snapshots=True, sources=True)
                self.page = self.browser.pages[0]
                self.browser.set_default_timeout(10000)
                self.page.set_default_timeout(10000)
                await self.log_info(f"操作: 启动浏览器成功, page对象: {str(self.page)}")
            # self.page.on('response', self.check_response)
        # self.page.on('response', self.check_response)
        self.page.on('dialog', self.handle_print_dialog)
        # await self.page.evaluate("location.reload()")
        # page.on('popup', self.on_popup)
        # self.page.on("request", lambda event: self.on_url_change(event["value"]))

    async def init_linux(self, browser_name="chromium"):
        # 谷歌浏览器
        cr_user_data_dir = "/ms-playwright/chromium-1091/user_data"  # 指定缓存目录，用相对路径
        cr_executable_path = "/ms-playwright/chromium-1091/chrome-linux/chrome"  # 指定可执行文件路径，用相对路径
        # 火狐浏览器
        ff_user_data_dir = "/ms-playwright/firefox-1429/firefox/user_data"  # 指定缓存目录，用相对路径
        ff_executable_path = "/ms-playwright/firefox-1429/firefox/firefox"  # 指定可执行文件路径，用相对路径
        # 苹果浏览器
        # wk_user_data_dir = "/ms-playwright/webkit-1921/user_data"  # 指定缓存目录，用相对路径
        # wk_executable_path = "/ms-playwright/webkit-1944/Playwright"  # 指定可执行文件路径，用相对路径
        if browser_name == "chromium":
            if os.path.exists(cr_user_data_dir):
                shutil.rmtree(cr_user_data_dir)
        else:
            if os.path.exists(ff_user_data_dir):
                shutil.rmtree(ff_user_data_dir)
        if browser_name == "chromium":
            self.browser = await self.playwright.chromium.launch_persistent_context(
                user_data_dir=cr_user_data_dir,
                executable_path=cr_executable_path,
                headless=True,
                viewport={'width': 1920, 'height': 1080},
                args=["--start-maximized", ]
            )
        elif browser_name == "firefox":
            self.browser = await self.playwright.firefox.launch_persistent_context(
                user_data_dir=ff_user_data_dir,
                executable_path=ff_executable_path,
                headless=True,
                viewport={'width': 1920, 'height': 1080},
                args=["--start-maximized", ]
            )
        self.page = self.browser.pages[0]
        self.browser.set_default_timeout(10000)
        self.page.set_default_timeout(10000)
        # self.page.on('response', self.check_response)
        # 监听对话框事件
        # self.page.on("dialog", self.handle_print_dialog)

    async def now(self):
        now_time = datetime.datetime.now()
        formatted_time = now_time.strftime(f"%Y-%m-%d %H:%M:%S.{now_time.strftime('%f')[:-3]}")  # 格式化输出
        return formatted_time

    async def on_close(self):
        print("Browser has been closed.")
        await self.browser.close()

    async def log_info(self, msg):
        msg = "%s - INFO - %s<br>" % (await self.now(), msg,)
        msg = msg.replace('"', "'")
        self.log_text += msg

    def this_info(self, msg):
        msg = "%s - INFO - %s<br>" % (now(), msg,)
        msg = msg.replace('"', "'")
        self.log_text += msg

    async def log_error(self, msg):
        msg = "<span style='color: red'>%s - ERROR - %s<span>" % (await self.now(), msg)
        self.log_text += msg

    async def close(self):
        await self.context.close()
        await self.browser.close()

    async def new_page(self):
        return await self.context.new_page()

    async def item_locator(self, locators):
        try:
            element = None
            # locators if isinstance(locators, list) else json.dumps(locators)
            for by_item in locators:
                by_name = by_item["by"]
                params = by_item["params"]
                args = None
                kwargs = {}
                # print("解析前的params：", params)
                if by_name != "first":
                    try:
                        if isinstance(params, str):
                            args = params
                        else:
                            for p_item in params:
                                if isinstance(p_item, str):
                                    args = p_item
                                else:
                                    key, value = p_item["key"], p_item["value"]
                                    kwargs[key] = value
                    except Exception as e:
                        traceback.print_exc()
                        print(e)
                element = await self.find_element(element, by_name, args, kwargs)
            return element
        except Exception:
            traceback.print_exc()
            raise Exception

    async def find_element(self, element, by, args, kwargs):
        # print(f"查看定位元素：{by}", args, kwargs)
        if element:
            my_page = element
        else:
            my_page = self.page
        # locator = json.loads(locator)
        try:
            if by == "locator":
                element = my_page.locator(args, **kwargs)  # text
            if by == "get_by_placeholder":
                element = my_page.get_by_placeholder(args, **kwargs)  # text
            elif by == "get_by_text":
                element = my_page.get_by_text(args, **kwargs)  # text
            elif by == "get_by_role":
                element = my_page.get_by_role(args, **kwargs)  # text
            elif by == "filter":
                res_locator = re.findall("re\.compile\(r\"(.*?)\"\'", str(kwargs))
                if res_locator:
                    element = my_page.filter(has_text=re.compile(r"%s" % (res_locator[0],)))
                else:
                    element = my_page.filter(args, **kwargs) if args else my_page.filter(**kwargs)
            elif by == "first":
                element = my_page.first  # text
            elif by == 'parent':
                element = my_page
                times = int(args) if args else 1
                for _ in range(times):
                    element = element.locator('..') 
            elif by == "nth":
                element = my_page.nth(int(args))  # text
            elif by == "xpath":
                element = my_page.locator(args, **kwargs)  # selector
            elif by == "class_name":
                element = my_page.locator(f'.{args}')
            elif by == "id":
                element = my_page.locator(f'#{args}')
            elif by == "name":
                element = await my_page.query_selector(f"[name='{args}']")
            elif by == "query_selector":
                element = await my_page.query_selector(args)  # selector
            elif by == "query_selector_all":
                lr = await args.split(",")
                element = await my_page.query_selector_all(f'.{lr[0]}')[int(lr[1]) - 1]
            await self.log_info(f"操作: 定位, 定位方式: {by}, 定位值: {str(kwargs)}")

            # 如果元素已经可见，调用 scroll_into_view_if_needed
            # scroll_into_view_if_needed 调用后，元素会处于稳定状态（Stable）
            # 详情见页面表格 https://playwright.dev/python/docs/actionability
            if await element.count() == 1 and await element.is_visible():
                await element.scroll_into_view_if_needed()
        except Exception as e:
            traceback.print_exc()
            await self.log_info(f"ERROR: 获取元素失败, 操作: 获取元素, 定位方式: {by}, 定位值: {str(kwargs)}")
            # traceback.print_exc()
            raise e
        return element

    async def goto(self, **kwargs):
        try:
            url = kwargs["params"]
            await self.page.goto(url, timeout=30000)
            await self.log_info(f"操作: 打开网页, 地址: {url}")
        except Exception as e:
            traceback.print_exc()
            raise e

    async def if_url_not_current_goto(self, **kwargs):
        try:
            url = kwargs["params"]
            current_url = self.page.url
            await self.log_info(f"判断当前URL是否为指定URL：当前url：{current_url}, 目标url：{url}")
            if url != current_url:
                await self.page.goto(url, timeout=30000)
                await self.log_info(f"操作: 打开网页, 地址: {url}")
        except Exception as e:
            traceback.print_exc()
            raise e

    async def fill(self, **kwargs):
        try:
            locators, text = kwargs["locators"], kwargs["params"]
            el = await self.item_locator(locators)
            await el.click()
            await self.page.keyboard.press("Control+A")
            await el.fill(text)
            await self.log_info(f"操作: 输入, 对象元素: {str(el)}, 输入值: {text}")
        except Exception as e:
            traceback.print_exc()
            raise e

    async def fill_phone_verify_code(self, **kwargs):
        try:
            locators, text = kwargs["locators"], kwargs["params"]
            el = await self.item_locator(locators)
            await el.click()
            await self.page.keyboard.press("Control+A")
            await el.fill(text)
            await self.log_info(f"操作: 输入, 对象元素: {str(el)}, 输入值: {text}")
        except Exception as e:
            traceback.print_exc()
            raise e

    async def click(self, **kwargs):
        try:
            locators = kwargs["locators"]
            el = await self.item_locator(locators)
            await el.click()
            await self.log_info(f"操作: 点击, 对象元素: {str(el)}")
        except Exception as e:
            traceback.print_exc()
            raise e

    async def sleep(self, **kwargs):
        second = kwargs["params"]
        if isinstance(second, str):
            second = eval(second)
        await asyncio.sleep(second)
        await self.log_info(f"操作: 固定等待(time_sleep), 等待时间: {second}")

    async def element_wait_for(self, **kwargs):
        """
        元素等待,参考 https://playwright.dev/python/docs/api/class-locator#locator-wait-for
        """
        locators, att_name = kwargs["locators"], kwargs["params"]
        el = await self.item_locator(locators)
        await el.wait_for(state=att_name)

    # --------------------------------------------获取方法----------------------------------------------------

    async def get_current_url(self, **kwargs):
        var_name = kwargs["result"]
        self.case_var_pool[var_name] = await self.page.evaluate('window.location.href')

    async def re_extract(self, **kwargs):
        params, var_name = kwargs["params"], kwargs["result"]
        string, pattern = params.split(',')
        result = re.findall(rf'{pattern}', string)
        if len(result) > 0:
            self.case_var_pool[var_name] = result[0]
        else:
            self.case_var_pool[var_name] = ""

    async def get_element_text(self, **kwargs):
        """
        获取元素展示文本
        :param kwargs: locators: 元素定位列表, var_name: 变量名
        :return:
        """
        locators, var_name = kwargs["locators"], kwargs["result"]
        el = await self.item_locator(locators)
        if (await el.evaluate('e => e.tagName')).lower() == 'input':
            text = el.input_value()
        else:
            text = await el.text_content()
        self.case_var_pool[var_name] = text
        await self.log_info(f"操作: 获取元素展示文本, Text: {text}")

    async def get_attribute(self, **kwargs):
        """
        获取元素属性值
        :param kwargs: locators: 元素定位列表, var_name: 变量名
        :return:
        """
        locators, att_name, var_name = kwargs["locators"], kwargs["params"], kwargs["result"]
        el = await self.item_locator(locators)
        att_value = await el.get_attribute(att_name)
        self.case_var_pool[var_name] = att_value
        await self.log_info(f"操作: 获取元素属性值, Text: {att_value}")

    async def image_code(self, **kwargs):
        """
        解析base64图片验证码
        :param kwargs: 【script】js脚本、【var_name】缓存结果变量名
        :return:
        """
        try:
            script, var_name = kwargs["params"], kwargs["result"]
            evaluate_res = await self.page.evaluate(script)
            await self.log_info(f"操作: 执行js, script: {script}, result: {evaluate_res}")
            image_bs64 = evaluate_res.split(",")[1]
            await self.log_info(f"操作: 获取图片验证码bs64数据, bs64: {image_bs64}")
            code = await self.get_img_code(image_bs64)
            await self.log_info(f"操作: bs64转字符验证码, 验证码: {code}")
            self.case_var_pool[var_name] = code
        except:
            traceback.print_exc()

    async def get_img_code(self, image_base64):
        try:
            url = "http://api.ttshitu.com/predict"
            data = {
                "username": "longyunhai",  # 图鉴用户名
                "password": "longhai123",  # 图鉴密码
                "typeid": 1003,
                "image": image_base64
            }
            res = requests.post(url=url, json=data)
            result = json.loads(res.text)
            await self.log_info(f"操作: 调用外部解析验证码, result: {str(result)}")
            if result["message"] == "success":
                img_code = result["data"]["result"]
                await self.log_info(f"操作: 解析图片验证码, img_code: {img_code}")
                return img_code
            else:
                await self.log_error("操作: 解析图片验证码, status: 解析验证码失败")
                return None
        except Exception as e:
            traceback.print_exc()
            print(e)
            raise e

    # 点击坐标 relative
    async def click_coordinate(self, **kwargs):
        try:
            position = kwargs["params"]
            position = position.split("\n")
            for item in position:
                p = item.split(",")
                x, y = int(p[0]), int(p[1])
                await self.page.mouse.click(x, y)
                await self.log_info(f"操作: 点击坐标, 坐标: x: {x}、y: {y}")
        except Exception as e:
            traceback.print_exc()
            raise e

    async def click_relative_coordinate(self, **kwargs):
        try:
            locators, position = kwargs["locators"], kwargs["params"]
            el = await self.item_locator(locators)
            bounding_box = await el.bounding_box()
            position = position.split("\n")
            for item in position:
                p = item.split(",")
                x, y = int(p[0]) + bounding_box["x"], int(p[1]) + bounding_box["y"]
                await self.page.mouse.click(x, y)
                await self.log_info(f"操作: 点击坐标, 坐标: x: {x}、y: {y}")
        except Exception as e:
            traceback.print_exc()
            raise e

    async def move_to_xy(self, **kwargs):
        position = kwargs["params"]
        p = position.split(",")
        x, y = int(p[0]), int(p[1])
        await self.page.mouse.move(x, y)
        await self.log_info(f"操作: 移动鼠标到坐标<move_to_xy>, 坐标: x: {x}、y: {y}")

    # ---------------------------------------------系统级别鼠标键盘操作----------------------------------------------------
    # 关闭打印弹窗
    async def close_print_window(self, **kwargs):
        pass

    async def reload(self, **kwargs):
        await self.page.reload()
        await self.log_info(f"操作: 重新加载页面")

    # 鼠标操作
    async def move_to_element(self, **kwargs):
        """
        鼠标移动到元素上
        :param locator_list: 元素定位列表
        :return:
        """
        locators = kwargs["locators"]
        el = await self.item_locator(locators)
        await el.hover()
        await self.log_info(f"操作: 悬浮, 对象元素: {str(el)}")

    # 拖动元素，偏移指定距离
    async def drag_offset(self, **kwargs):
        """
        拖动元素，偏移指定距离
        :param locator_list: 元素定位列表
        :param x_offset,y_offset X和Y方向偏移距离
        :return:
        """
        locators = kwargs["locators"]
        el = await self.item_locator(locators)
        box = await el.bounding_box()
        x, y = box["x"] + box["width"] / 2, box["y"] + box["height"] / 2  # 元素中心点
        x_offset, y_offset = kwargs['params'].replace(' ', '').strip().split(',')  # 偏移量
        x_target, y_target = x + int(x_offset), y + int(y_offset)  # 目标坐标

        await self.page.mouse.move(x, y)
        await self.page.mouse.down()
        await self.page.mouse.move(x_target, y_target, steps=10)  # 设置分10次完成，太快会导致失败
        await self.page.mouse.up()

    # 上传文件
    async def send_upload_file(self, **kwargs):
        # files = file_name.split('、')
        # for name in files:
        # img = ROOT_DIR + f"/static/user_photo/{name}.png"
        locators, file = kwargs["locators"], kwargs["params"]
        name, token = file["name"], file["token"]
        file_exists(name, token)
        file_path = f'files/{token}/{name}'
        if os.path.exists(file_path):
            el = await self.item_locator(locators)
            async with self.page.expect_file_chooser(timeout=5000) as fc_info:
                await el.click()  # 点击上传附件按钮
            file_chooser = await fc_info.value
            await file_chooser.set_files(file_path)

    # 缓存元素文本
    async def get_element_text(self, **kwargs):
        """
        获取元素展示文本
        :param locator_list: 元素定位列表
        :param var_name: 接收变量名
        :return:
        """
        locators, var_name = kwargs["locators"], kwargs["result"]
        el = await self.item_locator(locators)

        # input 使用 input_value 获取，其他使用 text_content 获取
        if (await el.evaluate('e => e.tagName')).lower() == 'input':
            text = await el.input_value()
        else:
            text = await el.text_content()
        self.case_var_pool[var_name] = text

    async def get_numbers_in_element_text(self, **kwargs):
        """
        获取元素文本中的数字
        :param locator_list: 元素定位列表
        :param index: 取结果中的下标元素
        :param var_name: 接收变量名
        :return:
        """
        locators, index, var_name = kwargs["locators"], kwargs["params"], kwargs["result"]
        el = await self.item_locator(locators)
        text = await el.text_content()
        pattern = r"\d+"
        res = re.findall(pattern, text)[index]
        await self.log_info(f"操作: 提取字符串中的数字, 字符串: {text}, 下标: {index}, 返回值: {res}")
        self.case_var_pool[var_name] = text

    # ---------------------------------------------键盘操作--------------------------------------------
    async def press(self, **kwargs):
        """
        操作键盘上的按键
        :param key:
        :return:
        """
        # 模拟按下键盘上的某个按键f
        key = kwargs["params"]
        await self.page.keyboard.press(key)
        await self.log_info(f"操作: 模拟键盘操作, 键为: {key}")

    #
    async def screenshot(self):
        """
        :return:
        """
        img_name = ''
        try:

            await asyncio.sleep(0.5)
            img_name = str(uuid4())
            img_path = f"./screenshot/{img_name}.jpg"
            await self.page.screenshot(path=img_path)
            # 存到oss文件服务器
            img_data = upload_file_to_oss(img_path, img_name)
            if img_data["status"] == 1000:
                img_url = img_data["url"]
            else:
                # 如果上传失败则上传到作者（yunhai）腾讯云服务器
                upload_file_to_simple_drawing(img_path, img_name)
                if img_data["status"] == 1000:
                    img_url = img_data["url"]
                else:
                    img_url = None
            self.image_urls.append(img_url)
            if os.path.exists(img_path):
                os.remove(img_path)
            # 存到mysql数据库的方法
            # img_bs64_data = img_gen_bs64(img_path)
            # Images.objects.create(uuid=img_name, data=img_bs64_data)
        except:
            pass
        await self.log_info(f"操作: 截屏, 文件名称: {img_name}")

    # 缓存操作
    async def storage_state(self):
        """
        保存登录信息
        """
        try:
            await self.page.wait_for_timeout(2)
            alias = self.global_var_pool["alias"]
            # 登录成功后，获取当前页面的存储状态
            storage_state = self.context.storage_state()
            with open(f'./{alias}_storage_state.json', 'w') as file:
                json.dump(storage_state, file)
            await self.log_info(f"操作: 保存登录状态到文件: {alias}_storage_state.json")
            await self.page.wait_for_timeout(1)
        except Exception as e:
            traceback.print_exc()
            await self.log_error(f"操作: 保存登录状态, 状态: 失败, 错误信息: {str(e)}")

    
    async def get_element_image(self, **kwargs):
        """
        获取元素的截图，并保存到 oss, 返回图片的 url
        """
        locators, var_name = kwargs["locators"], kwargs["result"]
        el = await self.item_locator(locators)

        # 元素截图
        img_path = f"./screenshot/{str(uuid4())}.jpg"
        await el.screenshot(path=img_path)

        # 截图上传
        img_data = upload_file_to_oss(img_path, '')
        if img_data["status"] != 1000:
            return None

        # 删除截图
        if os.path.exists(img_path):
            os.remove(img_path)

        self.case_var_pool[var_name] = img_data["url"]

        return img_data["url"]
    
    async def get_element_similarity(self, **kwargs):
        """
        获取图片的相似度，对照图片与当前元素图片
        """
        locators, var_name = kwargs["locators"], kwargs["result"]
        old_image_url = kwargs["params"]
        new_image_url = await self.get_element_image(locators=locators, result='temp')

        # 读取图片
        image1 = io.imread(old_image_url, as_gray=True)
        image2 = io.imread(new_image_url, as_gray=True)

        # 调整图像大小以确保尺寸相同
        min_height = min(image1.shape[0], image2.shape[0])
        min_width = min(image1.shape[1], image2.shape[1])
        image1 = resize(image1, (min_height, min_width))
        image2 = resize(image2, (min_height, min_width))

        # 计算结构相似性指数
        similarity_index, diff_image = ssim(image1, image2, full=True, data_range=1.0)
        self.case_var_pool[var_name] = similarity_index

        # 保存差距图片，并上传
        img_path = f"./screenshot/diff-{str(uuid4())}.jpg"
        diff_image_normalized = (diff_image * 255).astype(np.uint8)
        io.imsave(img_path, diff_image_normalized)
        img_data = upload_file_to_oss(img_path, '')
        if img_data["status"] != 1000:
            return None

        # 删除截图
        if os.path.exists(img_path):
            os.remove(img_path)
        diff_image_url = img_data['url']

        print(f'图片相似度: {similarity_index}')
        print(f'old image url: {old_image_url}\nnew image url: {new_image_url}\ndiff image url: {diff_image_url}')

    # 提供给判断（if control）使用-------------------------------------------------------
    async def element_is_enabled(self, **kwargs):
        """
        判断元素是否存在
        :param kwargs: locators：定位值列表
        :return: True或False
        """
        locators = kwargs["locators"]
        el = await self.item_locator(locators)
        is_enabled = await el.is_enabled()
        await self.log_info(f"操作: 查看元素是否存在, 对象元素: {str(el)}, 描述: 元素存在")
        return is_enabled

    async def get_attr_exist(self, **kwargs):
        """
        判断元素是否存在
        :param kwargs: locators：定位值列表
        :return: True或False
        """
        el = await self.item_locator(kwargs["locators"])
        attr = await el.get_attribute(kwargs["att_name"])
        attr = True if attr else False
        await self.log_info(f"操作: 获取元素属性, 属性是否存在: {attr}")
        return attr

    async def get_attr_val(self, **kwargs):
        """
        判断元素是否存在
        :param kwargs: locators：定位值列表
        :return: True或False
        """
        el = await self.item_locator(kwargs["locators"])
        att_value = await el.get_attribute(kwargs["att_name"])
        await self.log_info(f"操作: 获取元素属性值, Text: {att_value}")
        return att_value

    async def get_element_numbers(self, **kwargs):
        """
        判断元素是否存在
        :param kwargs: locators：定位值列表
        :return: True或False
        """
        selectors = await self.page.query_selector_all(kwargs["locator"])
        return len(selectors)

    # 断言操作--------------------------------------------------------------------
    async def assert_element_exist(self, **kwargs):
        locators = kwargs["locators"]
        print(locators)
        el = await self.item_locator(locators)
        print(el)
        assert await el.is_enabled()
        await self.log_info(f"操作: 断言元素存在, 对象元素: {str(el)}, 描述: 元素存在")

    async def assert_two_value(self, **kwargs):
        # expression_dict = self.re_replace_variable(expression_dict)
        expression_dict = kwargs["params"]
        left, right, condition = expression_dict["left"], expression_dict["right"], expression_dict["condition"]
        assert compare(left, condition, right)
        await self.log_info(f"操作: 断言两个值的关系, 值1: {left}, 值2: {right} 关系: {condition}")

    async def assert_element_not_exist(self, **kwargs):
        # try:
        locators = kwargs["locators"]
        el = await self.item_locator(locators)
        await el.is_enabled()
        await self.log_info(f"操作: 断言元素不存在, 对象元素: {str(el)}, 描述: 元素存在")
        assert False
        # except Exception as e:
        #     await self.log_info(f"操作: 断言元素不存在, 对象元素: {str(locator_list)}, 描述: 元素不存在")

    async def assert_element_attribute(self, **kwargs):
        locators = kwargs["locators"]
        el = await self.item_locator(locators)
        assert el.is_hidden()

    async def script(self, **kwargs):
        script, var_name = kwargs["params"], kwargs["result"]
        result = await self.page.evaluate(script)
        await self.log_info(f"操作: 执行js, script: {script}, result: {result}")
        self.case_var_pool[var_name] = result

    # 智能等待
    async def intelligent_wait(self, state):
        await self.page.wait_for_load_state(state)

    # ------------------------------------------监听方法------------------------------------------------
    # 监听新窗口事件
    def on_popup(self, popup):
        print("New popup page opened:", popup.url)

    async def handle_print_dialog(self, dialog):
        # 在这里处理打印机弹窗
        # 例如，可以点击确定按钮或取消按钮
        print("例如，可以点击确定按钮或取消按钮")
        await dialog.accept()  # 点击确定按钮

    # 监听url变化
    def on_url_change(url: str):
        print(f"URL changed to {url}")

    # 监听接口相应操作
    async def check_response(self, response):
        if self.extract_state:
            print("self.extract_path", self.extract_state, self.extract_path)
            # response.finished()
            # print("URL:{}".format(response.request.url))
            # https://test.snkoudai.com/api/produce/co/certificate/generateCertificateCode
            if self.extract_path in response.request.url:
                # print("URL:{}".format(response.request.url))
                # print("method:{}".format(response.request.method))
                # print("status:{}".format(response.status))
                body = await response.json()
                # print("body:{}".format(body))
                # print("timing:{} 毫秒".format(response.request.timing['responseEnd']))
        # print("******************************监听接口相应操作***************************************")

    # 调用流程
    async def call_process(self, **kwargs):
        case = kwargs["params"]
        params = {"case_id": case["id"]}
        res = requests.get("http://192.168.0.99:8001/api/web/playwrightGridMsg/case", params=params)
        return res.json()

    # 接口操作
    async def run_http_request(self, **kwargs):
        """
        执行HTTP请求
        :return:
        """
        # print("执行HTTP请求......")
        # print("case_var_pool中的kv：", self.case_var_pool)
        # 变量替换处理
        # body = json.loads(self.re_replace_variable(json.dumps(body)))
        body = kwargs["params"]
        method, url, headers, json_data = body["method"], body["url"], body["headers"], body["json"]
        # 处理headers
        headers = {item["key"]: item["value"] for item in headers} if len(headers) > 0 else None
        # print("处理url和请求参数：", url, headers, json_data)
        br = BaseRequest()
        response = br.request(method, url, headers=headers, json=json_data, extract_list=body["extract"])
        status_code, extract_result = response["status_code"], response["extract_result"]
        for k, v in extract_result.items():
            self.case_var_pool[k] = v
        await self.log_info(f"操作: 执行HTTP请求, method: {method}, url: {url}, status_code: {status_code}")

    def re_replace_variable(self, dict_or_string):
        """
        变量替换方法
        :param dict_or_string: 被替换的字典或字符串
        :return:
        """
        try:
            # 判断传入参数是否为字典
            new_string = json.dumps(dict_or_string, ensure_ascii=False) if isinstance(dict_or_string, (dict, list)) else dict_or_string
            # 判断数据源是否包含$符号
            if "$" in new_string:
                # 替换全局变量
                if "$GBL{" in new_string:
                    # print(self.global_var_pool)
                    if re.search("\S*\$GBL\{(.*?)\}\s*", new_string):
                        result = re.findall(".*?\$GBL\{(.*?)\}.*?", new_string)
                        for key in result:
                            if key in self.global_var_pool:
                                new_string = new_string.replace("$GBL{%s}" % (key,), str(self.global_var_pool[key]))

                # 替换环境变量
                if "$ENV{" in new_string:
                    # print(self.env_var_pool)
                    if re.search("\S*\$ENV\{(.*?)\}\s*", new_string):
                        result = re.findall(".*?\$ENV\{(.*?)\}.*?", new_string)
                        for key in result:
                            if key in self.env_var_pool:
                                new_string = new_string.replace("$ENV{%s}" % (key,), str(self.env_var_pool[key]))

                # 随机函数 ${__random_name()__}
                if "${__" in new_string:
                    if re.search("\S*\$\{__(.*?)\}\s*", new_string):
                        result = re.findall(".*?\$\{__(.*?)\}.*?", new_string)
                        for key in result:
                            res = key.split('(')
                            args = eval(f"({res[1]}")
                            if args:
                                result_data = getattr(my_faker, res[0])(args)
                            else:
                                result_data = getattr(my_faker, res[0])()
                            new_string = new_string.replace("${__%s}" % (key,), result_data)

                # 替换局部变量
                if "${" in new_string:
                    # print(self.case_var_pool)
                    if re.search("\S*\$\{(.*?)\}\s*", new_string):
                        result = re.findall(".*?\$\{(.*?)\}.*?", new_string)
                        for key in result:
                            new_string = new_string.replace("${%s}" % (key,), str(self.case_var_pool[key]))

            # 如果传入的dict字典数据，则转为字典返回，反之则返回字符串
            replace_result = json.loads(new_string) if isinstance(dict_or_string, (dict, list)) else new_string
            return replace_result
        except Exception as e:
            traceback.print_exc()
            raise ValueError(f"变量替换处理异常：{str(e)}")


if __name__ == '__main__':
    s = [{'type': 'compare_two_value', 'left': '$ENV{base_url}', 'right': 'https://snkoudai.com', 'condition': 'equal', 'by': '', 'locator': '', 'attr': '', 'attr_val': '', 'ele_num': ''}]
    new_string = json.dumps(s, ensure_ascii=False) if isinstance(s, (dict, list)) else s
    print(type(new_string))
